import { useDispatch } from 'react-redux';
import { IAppDispatch, IRootState } from './index';
import { CaseReducerActions, SliceCaseReducers } from '@reduxjs/toolkit';

export const getTypePrefix = <T extends string>(key: T = '' as T) =>
  `set_${key}` as const;

// 获取set的类型
export type TSetType<T extends string> = ReturnType<typeof getTypePrefix<T>>;

export type TMakeReducersReturnType<T extends IAnyObject> = {
  [K in keyof T as TSetType<K & string>]: (state: any) => void;
};

/**
 * @desc 精简模板代码
 * @param type
 * @param initState
 * @returns {Function}
 */
export const makeReducer =
  (key: string) =>
  (state, action): void => {
    state[key] = action.payload;
  };

/**
 * @desc 根据对象生成reducer，type命名规则：`set_${key}`
 */
export const makeReducers = <T extends IAnyObject>(
  obj: T,
): TMakeReducersReturnType<T> => {
  const reducers = {} as TMakeReducersReturnType<T>;

  for (const [key, val] of Object.entries(obj)) {
    const actionKey = getTypePrefix(key) as TSetType<keyof T & string>;
    // 如果已经是一个reducer，不进行包装
    reducers[actionKey] = typeof val === 'function' ? val : makeReducer(key);
  }

  return reducers;
};

/**
 * @desc 生成更新reducer函数
 */
export const makeUpdateFn =
  <T extends IAnyObject>(
    actions: CaseReducerActions<SliceCaseReducers<T>, string>,
  ) =>
  (obj: Partial<T>, dispatch: IAppDispatch): void => {
    // 可以支持批量更新
    for (const [key, val] of Object.entries(obj)) {
      dispatch(actions[getTypePrefix(key)](val));
    }
  };

type TDispatchPromiseActionType<T = unknown> = (
  dispatch: IDispatch | ((action: TDispatchPromiseActionType<T>) => Promise<T>),
  getState: () => IRootState,
) => T;
/**
 * @desc promise版的dispatch，只适用于hooks
 * @example
 * const dispatchPro = useDispatchPromise();
 * await useDispatchPromise(action);
 */
export const useDispatchPromise = () => {
  const dispatch = useDispatch();

  const dispatchPromise = <T = unknown>(
    action: TDispatchPromiseActionType<T>,
  ): Promise<T> =>
    new Promise((rs, rj) => {
      // 这里dispatch一次，为了拿到getState
      dispatch((async (__: any, getState: any) => {
        try {
          let ret;

          if (typeof action === 'function') {
            ret = await action(dispatchPromise, getState);
          } else {
            ret = dispatch(action);
          }

          rs(ret);
        } catch (err) {
          rj(err);
        }
      }) as any);
    });

  return dispatchPromise;
};
